﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.SessionState;
using System.Configuration;
using System.Web;
using RedisSessionStore.Redis;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO.Compression;

namespace RedisSessionStore.WithLock
{
	public class RedisWithLockSessionStateStoreProvider : RedisSessionStore.NoLock.RedisNoLockSessionStateStoreProvider
	{
		private readonly TimeSpan LockTimeout = TimeSpan.FromSeconds(5); //TODO: Move to config

		public override SessionStateStoreData CreateNewStoreData(System.Web.HttpContext context, int timeout)
		{
			return CreateLegitStoreData(context, (ISessionStateItemCollection)null, (HttpStaticObjectsCollection)null, timeout);
		}

		public override void CreateUninitializedItem(System.Web.HttpContext context, string id, int timeout)
		{
			var data = this.CreateNewStoreData(context, timeout);
			Put(id, data);
		}

		public override SessionStateStoreData GetItem(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
		{
			//TODO: Check parameters
			lockAge = TimeSpan.Zero;
			lockId = null;
			locked = false;
			actions = SessionStateActions.None;

			return Get(id);
		}

		public override SessionStateStoreData GetItemExclusive(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
		{
			using (var redis = SingleRedisPool.GetClient(_redisServer))
			{
				//Trying to acquire lock
				try
				{
					var redisLock = redis.AcquireLock(id + "_lock", LockTimeout);

					//We get to this line only if we acquired lock, filling params
					lockId = redisLock;
					locked = true;
					lockAge = TimeSpan.Zero; //We don't know how long it was locked :(
					actions = SessionStateActions.None;

					//Getting our locked id
					return Deserialize(redis.Get<byte[]>(id));
				}
				catch (TimeoutException timeoutEx)
				{
					//Couldn't lock item
					lockId = null;
					locked = false;
					lockAge = TimeSpan.Zero;
					actions = SessionStateActions.None;
					throw;
				}
			}
		}

		public override void ReleaseItemExclusive(System.Web.HttpContext context, string id, object lockId)
		{
			var redisLock = lockId as IDisposable;
			if (redisLock != null)
				redisLock.Dispose();
		}

		public override void RemoveItem(System.Web.HttpContext context, string id, object lockId, SessionStateStoreData item)
		{
			using (var redis = SingleRedisPool.GetClient(_redisServer))
			{
				redis.Remove(id);
			}
		}

		public override void SetAndReleaseItemExclusive(System.Web.HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
		{
			if (!newItem)
				ReleaseItemExclusive(context, id, lockId);

			Put(id, item);
		}

		#region Utility
		private void Put(string id, SessionStateStoreData item)
		{
			using (var redis = SingleRedisPool.GetClient(_redisServer))
			{
				bool result = redis.Set(id, Serialize(item));
			}
		}

		private SessionStateStoreData Get(string id)
		{
			using (var redis = SingleRedisPool.GetClient(_redisServer))
			{
				return Deserialize(redis.Get<byte[]>(id));
			}
		}

		private byte[] Serialize(SessionStateStoreData data)
		{
			byte[] buf;
			int length;
			SerializeStoreData(data, 0, out buf, out length, false);
			return buf;
		}

		internal static void SerializeStoreData(SessionStateStoreData item, int initialStreamSize, out byte[] buf, out int length, bool compressionEnabled)
		{
			using (MemoryStream memoryStream = new MemoryStream(initialStreamSize))
			{
				SerializeInternal(item, (Stream)memoryStream);
				if (compressionEnabled)
				{
					byte[] buffer = memoryStream.GetBuffer();
					int count = (int)memoryStream.Length;
					memoryStream.SetLength(0L);
					using (DeflateStream deflateStream = new DeflateStream((Stream)memoryStream, CompressionMode.Compress, true))
						deflateStream.Write(buffer, 0, count);
					memoryStream.WriteByte(byte.MaxValue);
				}
				buf = memoryStream.GetBuffer();
				length = (int)memoryStream.Length;
			}
		}

		internal static void SerializeInternal(SessionStateStoreData item, Stream stream)
		{
			bool flag1 = true;
			bool flag2 = true;
			BinaryWriter writer = new BinaryWriter(stream);
			writer.Write(item.Timeout);
			if (item.Items == null || item.Items.Count == 0)
				flag1 = false;
			writer.Write(flag1);
			if (item.StaticObjects == null || item.StaticObjects.NeverAccessed)
				flag2 = false;
			writer.Write(flag2);
			if (flag1)
				((SessionStateItemCollection)item.Items).Serialize(writer);
			if (flag2)
				item.StaticObjects.Serialize(writer);
			writer.Write(byte.MaxValue);
		}

		private SessionStateStoreData Deserialize(byte[] data)
		{
			using (MemoryStream ms = new MemoryStream(data))
			{
				return DeserializeStoreData(HttpContext.Current, ms, false);
			}
		}

		internal static SessionStateStoreData DeserializeStoreData(HttpContext context, Stream stream, bool compressionEnabled)
		{
			if (compressionEnabled)
			{
				using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress, true))
					return DeserializeInternal(context, (Stream)deflateStream);
			}
			else
				return DeserializeInternal(context, stream);
		}

		internal static SessionStateStoreData DeserializeInternal(HttpContext context, Stream stream)
		{
			int timeout;
			SessionStateItemCollection stateItemCollection;
			HttpStaticObjectsCollection staticObjects;
			try
			{
				BinaryReader reader = new BinaryReader(stream);
				timeout = reader.ReadInt32();
				bool flag1 = reader.ReadBoolean();
				bool flag2 = reader.ReadBoolean();
				stateItemCollection = !flag1 ? new SessionStateItemCollection() : SessionStateItemCollection.Deserialize(reader);
				staticObjects = !flag2 ? SessionStateUtility.GetSessionStaticObjects(context) : HttpStaticObjectsCollection.Deserialize(reader);
				if ((int)reader.ReadByte() != (int)byte.MaxValue)
					throw new HttpException("Invalid_session_state");
			}
			catch (EndOfStreamException ex)
			{
				throw new HttpException("Invalid_session_state");
			}
			return new SessionStateStoreData((ISessionStateItemCollection)stateItemCollection, staticObjects, timeout);
		}

		private static SessionStateStoreData CreateLegitStoreData(HttpContext context, ISessionStateItemCollection sessionItems, HttpStaticObjectsCollection staticObjects, int timeout)
		{
			if (sessionItems == null)
				sessionItems = (ISessionStateItemCollection)new SessionStateItemCollection();
			if (staticObjects == null && context != null)
				staticObjects = SessionStateUtility.GetSessionStaticObjects(context);
			return new SessionStateStoreData(sessionItems, staticObjects, timeout);
		}
		#endregion
	}
}
